﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Interactivity;
using System.Collections.Generic;
using System.Windows.Markup;
using System.Windows.Threading;
using System.Windows.Controls.Primitives;
using System.Diagnostics;

namespace ICodeShare.UI.Interactivity
{
    [Flags]
    public enum scrollBarUsage
    {
        NotInUse = 0,
        VerticalBar = 1,
        HorizontalBar = 2
    }

    public class TouchScrollBehavior : Behavior<FrameworkElement>
    {
        #region Instance Properties

        private ScrollViewer associatedScrollViewer;
        private ListBox associatedListBox;
        private ScrollBar associatedVerticalScrollBar;
        private ScrollBar associatedHorizontalScrollBar;

        private readonly DispatcherTimer momentumDispatchTimer = new DispatcherTimer();
        private Point currentPosition;
        private Point previousPosition;
        private Point scrollOffsetStartPostion = new Point();
        private Point initialOffsetPosition;
        private Point initialMouseDownPosition;
        private Point scrollStartPosition;
        private Point scrollTargetPosition = new Point();
        private Vector inertialMagnitude = new Vector();
        private bool isTouchCaptured = false;
        private double defaultScrollBarWidth;
        private scrollBarUsage isScrollBarInUse = scrollBarUsage.NotInUse;
        private double frictionValue;
        private bool canBounce = false;

        private Storyboard scrollUpStory = null;
        private Storyboard scrollDownStory = null;
        private Storyboard scrollLeftStory = null;
        private Storyboard scrollRightStory = null;

        private bool isTouchInListbox = false;

        #endregion

        #region Dependency Properties

        public static readonly DependencyProperty AnimateScrollBarProperty =
            DependencyProperty.Register("AnimateScrollBar", typeof(bool), typeof(TouchScrollBehavior), new PropertyMetadata(false));

        public bool AnimateScrollBar
        {
            get { return (bool)GetValue(AnimateScrollBarProperty); }
            set { SetValue(AnimateScrollBarProperty, value); }
        }

        public static readonly DependencyProperty FrictionProperty =
            DependencyProperty.Register("Friction", typeof(double), typeof(TouchScrollBehavior), new PropertyMetadata(.02));

        public double Friction
        {
            get { return (double)GetValue(FrictionProperty); }
            set { SetValue(FrictionProperty, value); }
        }

        public static readonly DependencyProperty SpeedFactorProperty =
            DependencyProperty.Register("SpeedFactor", typeof(double), typeof(TouchScrollBehavior), new PropertyMetadata(1.0));

        public double SpeedFactor
        {
            get { return (double)GetValue(SpeedFactorProperty); }
            set { SetValue(SpeedFactorProperty, value); }
        }

        public static readonly DependencyProperty TrackFactorProperty =
            DependencyProperty.Register("TrackFactor", typeof(double), typeof(TouchScrollBehavior), new PropertyMetadata(1.0));

        public double TrackFactor
        {
            get { return (double)GetValue(TrackFactorProperty); }
            set { SetValue(TrackFactorProperty, value); }
        }

        #endregion

        protected override void OnAttached()
        {
            base.OnAttached();

            associatedListBox = this.AssociatedObject as ListBox;

            if (associatedListBox != null)
            {
                associatedListBox.SetValue(ScrollViewer.CanContentScrollProperty, false);
                if (associatedListBox.ReadLocalValue(ItemsControl.ItemsPanelProperty) == DependencyProperty.UnsetValue)
                {
                    System.IO.StringReader stringReader = new System.IO.StringReader(@"
						<ItemsPanelTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
							<StackPanel/>
						</ItemsPanelTemplate>");
                    System.Xml.XmlReader xmlReader = System.Xml.XmlReader.Create(stringReader);
                    associatedListBox.ItemsPanel = (ItemsPanelTemplate)XamlReader.Load(xmlReader);
                }
            }

            this.associatedScrollViewer = this.FindChild<ScrollViewer>(this.AssociatedObject);
            if (this.associatedScrollViewer != null)
            {
                this.Initialize();
            }
            else
                this.AssociatedObject.Loaded += this.HandleLoaded;
        }


        protected override void OnDetaching()
        {
            base.OnDetaching();

            if (associatedScrollViewer != null)
            {
                momentumDispatchTimer.Tick -= MonentumCalculationTick;
                associatedScrollViewer.RemoveHandler(FrameworkElement.MouseLeftButtonUpEvent, (MouseButtonEventHandler)ScrollViewerMouseLeftButtonUp);
                associatedScrollViewer.MouseMove -= new MouseEventHandler(ParentMouseMove);
                associatedScrollViewer.RemoveHandler(FrameworkElement.MouseLeftButtonDownEvent, (MouseButtonEventHandler)ScrollViewerMouseLeftButtonDown);

                if (associatedVerticalScrollBar != null)
                {
                    associatedVerticalScrollBar.RemoveHandler(FrameworkElement.MouseLeftButtonDownEvent, (MouseButtonEventHandler)(VerticalScrollBarMouseLeftButtonDown));
                    associatedVerticalScrollBar.RemoveHandler(FrameworkElement.MouseLeftButtonUpEvent, (MouseButtonEventHandler)(ScrollBarMouseLeftButtonUp));
                }

                if (associatedHorizontalScrollBar != null)
                {
                    associatedHorizontalScrollBar.RemoveHandler(FrameworkElement.MouseLeftButtonDownEvent, (MouseButtonEventHandler)(HorizontalScrollBarMouseLeftButtonDown));
                    associatedHorizontalScrollBar.RemoveHandler(FrameworkElement.MouseLeftButtonUpEvent, (MouseButtonEventHandler)(ScrollBarMouseLeftButtonUp));
                }
            }
        }

        private void HandleLoaded(object sender, EventArgs e)
        {
            this.AssociatedObject.Loaded -= this.HandleLoaded;

            this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                new Action(
                    delegate()
                    {
                        this.associatedScrollViewer = this.FindChild<ScrollViewer>(this.AssociatedObject);
                        if (this.associatedScrollViewer != null)
                        {
                            this.Initialize();
                        }
                    }
            ));
        }

        private void Initialize()
        {
            momentumDispatchTimer.Interval = new TimeSpan(0, 0, 0, 0, 20);
            momentumDispatchTimer.Tick += MonentumCalculationTick;

            associatedVerticalScrollBar = FindScrollBar(associatedListBox, Orientation.Vertical);
            associatedHorizontalScrollBar = FindScrollBar(associatedListBox, Orientation.Horizontal);

            if (associatedVerticalScrollBar != null)
            {
                associatedVerticalScrollBar.AddHandler(FrameworkElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(VerticalScrollBarMouseLeftButtonDown), true);
                associatedVerticalScrollBar.AddHandler(FrameworkElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(ScrollBarMouseLeftButtonUp), true);
                defaultScrollBarWidth = associatedVerticalScrollBar.Width;
            }

            if (associatedHorizontalScrollBar != null)
            {
                associatedHorizontalScrollBar.AddHandler(FrameworkElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(HorizontalScrollBarMouseLeftButtonDown), true);
                associatedHorizontalScrollBar.AddHandler(FrameworkElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(ScrollBarMouseLeftButtonUp), true);
                defaultScrollBarWidth = associatedHorizontalScrollBar.Height;
            }

            associatedScrollViewer.AddHandler(FrameworkElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(ScrollViewerMouseLeftButtonUp), true);
            associatedScrollViewer.MouseMove += new MouseEventHandler(ParentMouseMove);
            associatedScrollViewer.AddHandler(FrameworkElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(ScrollViewerMouseLeftButtonDown), true);

            var listBox = this.AssociatedObject as ListBox;
            var v1 = listBox.ReadLocalValue(ListBox.RenderTransformProperty);
            if (listBox.ReadLocalValue(ListBox.RenderTransformProperty) == DependencyProperty.UnsetValue)
            {
                System.IO.StringReader stringReader = new System.IO.StringReader(@"
                    <TransformGroup xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
                        <TranslateTransform/>
                    </TransformGroup>");
                System.Xml.XmlReader xmlReader = System.Xml.XmlReader.Create(stringReader);
                listBox.RenderTransform = (Transform)XamlReader.Load(xmlReader);
                
                string TopLeft = @"
                    <Storyboard AutoReverse='True' xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
			            <DoubleAnimationUsingKeyFrames BeginTime='00:00:00' Storyboard.TargetProperty='(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.{0})'>
                            <LinearDoubleKeyFrame Value='8' KeyTime='00:00:.08'/>
                            <LinearDoubleKeyFrame Value='10' KeyTime='00:00:.10'/>
				        </DoubleAnimationUsingKeyFrames>
		            </Storyboard>";
                stringReader = new System.IO.StringReader(string.Format(TopLeft, "Y"));
                xmlReader = System.Xml.XmlReader.Create(stringReader);
                scrollUpStory = (Storyboard)XamlReader.Load(xmlReader); 
                Storyboard.SetTarget(scrollUpStory.Children[0], listBox);

                stringReader = new System.IO.StringReader(string.Format(TopLeft, "X"));
                xmlReader = System.Xml.XmlReader.Create(stringReader);
                scrollLeftStory = (Storyboard)XamlReader.Load(xmlReader);
                Storyboard.SetTarget(scrollLeftStory.Children[0], listBox);

                string BottomRight = @"
                    <Storyboard AutoReverse='True' xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
			            <DoubleAnimationUsingKeyFrames BeginTime='00:00:00' Storyboard.TargetProperty='(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.{0})'>
                            <LinearDoubleKeyFrame Value='8' KeyTime='00:00:.08'/>
                            <LinearDoubleKeyFrame Value='10' KeyTime='00:00:.10'/>
				        </DoubleAnimationUsingKeyFrames>
		            </Storyboard>";
                stringReader = new System.IO.StringReader(string.Format(BottomRight, "Y"));
                xmlReader = System.Xml.XmlReader.Create(stringReader);
                scrollDownStory = (Storyboard)XamlReader.Load(xmlReader);
                Storyboard.SetTarget(scrollDownStory.Children[0], listBox);

                stringReader = new System.IO.StringReader(string.Format(BottomRight, "X"));
                xmlReader = System.Xml.XmlReader.Create(stringReader);
                scrollRightStory = (Storyboard)XamlReader.Load(xmlReader);
                Storyboard.SetTarget(scrollRightStory.Children[0], listBox);

                frictionValue = 1 - Friction;
            }
        }

        void VerticalScrollBarMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            isScrollBarInUse = scrollBarUsage.VerticalBar;
            ScrollBarMouseDownHelper(sender, e);
            associatedVerticalScrollBar.Width *= TrackFactor;
        }

        void HorizontalScrollBarMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            isScrollBarInUse = scrollBarUsage.HorizontalBar;
            ScrollBarMouseDownHelper(sender, e);
            associatedHorizontalScrollBar.Height *= TrackFactor;
        }

        void ScrollBarMouseDownHelper(object sender, MouseButtonEventArgs e)
        {
            canBounce = true;
            isTouchCaptured = true;
            initialOffsetPosition = new Point(associatedScrollViewer.HorizontalOffset, associatedScrollViewer.VerticalOffset);
            initialMouseDownPosition = e.GetPosition(associatedScrollViewer);
            currentPosition = initialMouseDownPosition;
            previousPosition = initialMouseDownPosition;
        }

        void ScrollBarMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            isTouchCaptured = false;

            associatedVerticalScrollBar.Width = associatedHorizontalScrollBar.Height = defaultScrollBarWidth;

            currentPosition = e.GetPosition(associatedScrollViewer);

            if (isScrollBarInUse == scrollBarUsage.HorizontalBar)
            {
                inertialMagnitude.X = (currentPosition.X - previousPosition.X) * SpeedFactor;
            }

            if (isScrollBarInUse == scrollBarUsage.VerticalBar)
            {
                inertialMagnitude.Y = (currentPosition.Y - previousPosition.Y) * SpeedFactor;
            }

            scrollTargetPosition.X = associatedScrollViewer.HorizontalOffset;
            scrollTargetPosition.Y = associatedScrollViewer.VerticalOffset; //_initialOffset.Y + delta.Y;

            previousPosition = currentPosition;

            if (AnimateScrollBar)
                momentumDispatchTimer.Start();
        }

        void ScrollViewerMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            inertialMagnitude.X = 0;
            inertialMagnitude.Y = 0;

            // if timer is active, stop it
            momentumDispatchTimer.Stop();

            isTouchInListbox = isScrollBarInUse == scrollBarUsage.NotInUse;

            if (isScrollBarInUse != scrollBarUsage.NotInUse)
                return;

            associatedScrollViewer.CaptureMouse();

            scrollStartPosition = e.GetPosition(associatedScrollViewer);
            scrollOffsetStartPostion.X = associatedScrollViewer.HorizontalOffset;
            scrollOffsetStartPostion.Y = associatedScrollViewer.VerticalOffset;

           momentumDispatchTimer.Start();
            isTouchCaptured = true;
        }

        void ParentMouseMove(object sender, MouseEventArgs e)
        {
            if (isTouchInListbox)
                ListBoxMouseMove(sender, e);
            else
                ScrollBarMouseMove(sender, e);
        }
        void ListBoxMouseMove(object sender, MouseEventArgs e)
        {
            Point delta;

            if (isTouchCaptured)
            {
                currentPosition = e.GetPosition(associatedScrollViewer);

                Point currentPoint = e.GetPosition(associatedScrollViewer);

                // Determine the new amount to scroll.
                delta = new Point(scrollStartPosition.X - currentPoint.X, scrollStartPosition.Y - currentPoint.Y);

                scrollTargetPosition.X = scrollOffsetStartPostion.X + delta.X;
                scrollTargetPosition.Y = scrollOffsetStartPostion.Y + delta.Y;

                // Scroll to the new position.
                associatedScrollViewer.ScrollToHorizontalOffset(scrollTargetPosition.X);
                associatedScrollViewer.ScrollToVerticalOffset(scrollTargetPosition.Y);
            }
        }
        void ScrollBarMouseMove(object sender, MouseEventArgs e)
        {
            Point delta;

            if (isTouchCaptured)
            {
                previousPosition = currentPosition;
                currentPosition = e.GetPosition(associatedScrollViewer);

                delta = new Point(currentPosition.X - initialMouseDownPosition.X, currentPosition.Y - initialMouseDownPosition.Y);

                if (AnimateScrollBar)
                {
                    if (isScrollBarInUse == scrollBarUsage.HorizontalBar)
                    {
                        if (associatedScrollViewer.HorizontalOffset == 0 && delta.X < 0 && canBounce)
                        {
                            canBounce = false;
                            scrollLeftStory.Begin();
                        }
                        if (associatedScrollViewer.HorizontalOffset == associatedScrollViewer.ScrollableWidth && delta.X > 0 && canBounce)
                        {
                            canBounce = false;
                            scrollRightStory.Begin();
                        }
                    }

                    if (isScrollBarInUse == scrollBarUsage.VerticalBar)
                    {
                        if (associatedScrollViewer.VerticalOffset == 0 && delta.Y < 0 && canBounce)
                        {
                            canBounce = false;
                            scrollUpStory.Begin();
                        }
                        if (associatedScrollViewer.VerticalOffset == associatedScrollViewer.ScrollableHeight && delta.Y > 0 && canBounce)
                        {
                            canBounce = false;
                            scrollDownStory.Begin();
                        }
                    }
                }
            }
        }

        void ScrollViewerMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            associatedScrollViewer.ReleaseMouseCapture();
            isTouchCaptured = false;
            isScrollBarInUse = scrollBarUsage.NotInUse;
        }




        private void MonentumCalculationTick(object sender, EventArgs e)
        {
            if (isTouchCaptured)
                HandleTouch(sender, e);
            else
                HandleTouchLifted(sender, e);
        }
        private double InertialCalculationNewValueBias(double originalMagnitude, double newMagnitude)
        {
            Debug.WriteLine("Heavy");
            return originalMagnitude * .1 + newMagnitude * .9 * SpeedFactor;
        }
        private double InertialCalculationOldValueBias(double originalMagnitude, double newMagnitude)
        {
            Debug.WriteLine("Unbuffered");
            return originalMagnitude * .9 + newMagnitude * .1 * SpeedFactor;
        }
        private double InertialCalculationStandardBias(double originalMagnitude, double newMagnitude)
        {
            Debug.WriteLine("Buffered");
            return originalMagnitude * .3 + newMagnitude * .7 * SpeedFactor;
        }

        private void HandleTouch(object sender, EventArgs e)
        {
            double currentMagnitudeX = previousPosition.X - currentPosition.X;
            double currentMagnitudeY = previousPosition.Y - currentPosition.Y;


            if (currentMagnitudeX == 0)
                inertialMagnitude.X = InertialCalculationOldValueBias(inertialMagnitude.X, currentMagnitudeY);
            else if (currentMagnitudeY * inertialMagnitude.Y < 0)
                inertialMagnitude.Y = InertialCalculationNewValueBias(inertialMagnitude.Y, currentMagnitudeY);
            else
                inertialMagnitude.Y = InertialCalculationStandardBias(inertialMagnitude.Y, currentMagnitudeY);

            if (currentMagnitudeY == 0)
                inertialMagnitude.Y = InertialCalculationOldValueBias(inertialMagnitude.Y, currentMagnitudeY);
            else if (currentMagnitudeY * inertialMagnitude.Y < 0)
                inertialMagnitude.Y = InertialCalculationNewValueBias(inertialMagnitude.Y, currentMagnitudeY);
            else
                inertialMagnitude.Y = InertialCalculationStandardBias(inertialMagnitude.Y, currentMagnitudeY);

            Debug.WriteLine("previousPosition.Y - currentPosition.Y:" + (previousPosition.Y - currentPosition.Y).ToString() + " - inertialMagnitude.Y:" + Math.Round(inertialMagnitude.Y));
            previousPosition = currentPosition;
        }
        private void HandleTouchLifted(object sender, EventArgs e)
        {
            if (inertialMagnitude.Length > 1)
            {
                if (isScrollBarInUse != scrollBarUsage.VerticalBar)
                    associatedScrollViewer.ScrollToHorizontalOffset(scrollTargetPosition.X);
                if (isScrollBarInUse != scrollBarUsage.HorizontalBar)
                    associatedScrollViewer.ScrollToVerticalOffset(scrollTargetPosition.Y);
                scrollTargetPosition.X += inertialMagnitude.X;
                scrollTargetPosition.Y += inertialMagnitude.Y;
                inertialMagnitude *= frictionValue;
            }
            else
            {
                momentumDispatchTimer.Stop();
            }
        }
        private T FindChild<T>(DependencyObject target) where T : DependencyObject
        {
            Queue<DependencyObject> queue = new Queue<DependencyObject>();

            queue.Enqueue(target);
            while (queue.Count > 0)
            {
                DependencyObject current = queue.Dequeue();

                if (typeof(T).IsAssignableFrom(current.GetType()))
                    return (T)current;

                int childCount = VisualTreeHelper.GetChildrenCount(current);
                for (int i = 0; i < childCount; ++i)
                {
                    queue.Enqueue(VisualTreeHelper.GetChild(current, i));
                }
            }

            return null;
        }

        private ScrollBar FindScrollBar(DependencyObject target, Orientation orientation)
        {
            Queue<DependencyObject> queue = new Queue<DependencyObject>();

            queue.Enqueue(target);
            while (queue.Count > 0)
            {
                DependencyObject current = queue.Dequeue();

                if (typeof(ScrollBar).IsAssignableFrom(current.GetType()))
                {
                    ScrollBar sc = current as ScrollBar;
                    if (sc.Orientation == orientation)
                        return (ScrollBar)current;
                }

                int childCount = VisualTreeHelper.GetChildrenCount(current);
                for (int i = 0; i < childCount; ++i)
                {
                    queue.Enqueue(VisualTreeHelper.GetChild(current, i));
                }
            }
            return null;
        }
    }
}
